﻿namespace Blaze.Framework.Pooling
{
    using System;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Assertions;
    using Object = UnityEngine.Object;

    /// <summary>
    /// 用于管理<see cref="GameObject"/>的对象池。
    /// </summary>
    public class GameObjectPool : ObjectPool<GameObject>, IDisposable
    {
        /// <summary>
        /// 获取对象池的编号。
        /// </summary>
        public int Id { get; private set; }

        #region IDisposable Members

        public void Dispose()
        {
            Clear();
            mPrefab = null;
            Object.Destroy(mRootObject);
        }

        #endregion

        /// <summary>
        /// 构造一个<see cref="GameObjectPool"/>。
        /// </summary>
        /// <param name="id">对象池的唯一编号</param>
        /// <param name="prefab"></param>
        /// <param name="poolRoot"></param>
        /// <param name="initialCount"></param>
        /// <param name="type"></param>
        public GameObjectPool(int id, Object prefab, Transform poolRoot, int initialCount, PoolInflationType type, bool autoActive)
        {
            Assert.IsNotNull(prefab, "[ObjPoolManager]prefab can't be null!");
            mAutoActive = autoActive;
            mPrefab = prefab;
            Id = id;
            mInflationType = type;
            mRootObject = new GameObject(prefab.name);
            mRootObject.transform.SetParent(poolRoot.transform, false);
            populatePool(Mathf.Max(initialCount, 1));
        }

        /// <summary>
        /// 清理整个对象池。
        /// </summary>
        public void Clear()
        {
#if UNITY_EDITOR
            if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
                return;
#endif
            while (mAvailableObjects.Count > 0)
            {
                var obj = mAvailableObjects.Pop();
                if (obj.gameObject != null)
                    Object.Destroy(obj.gameObject);
            }
        }

        /// <summary>
        /// 从对象池中获取一个对象。
        /// </summary>
        /// <returns>可用的对象</returns>
        public override GameObject Get()
        {
            return getNextAvailableObject(mAutoActive);
        }

        /// <summary>
        /// 将指定的对象回收到对象池。
        /// </summary>
        /// <param name="obj">对象</param>
        public override void Put(GameObject obj)
        {
            var poolObject = obj.GetComponent<PoolObject>();
            returnObjectToPool(poolObject);
        }

        /// <summary>
        /// 当需要创建一个新的对象时调用此方法。
        /// </summary>
        /// <returns></returns>
        protected override GameObject Create()
        {
            var go = (GameObject) Object.Instantiate(mPrefab);
            var po = go.AddComponent<PoolObject>();
            po.PoolId = Id;
            return go;
        }

        private GameObject getNextAvailableObject(bool autoActive)
        {
            PoolObject po = null;
            if (mAvailableObjects.Count > 0)
            {
                po = mAvailableObjects.Pop();
            }
            else
            {
                int increaseSize = 0;
                //increment size var, this is for info purpose only
                if (mInflationType == PoolInflationType.Increment)
                {
                    increaseSize = 1;
                }
                else if (mInflationType == PoolInflationType.Double)
                {
                    increaseSize = mAvailableObjects.Count + Mathf.Max(mInUseCount, 0);
                }
                if (increaseSize > 0)
                {
                    populatePool(increaseSize);
                    po = mAvailableObjects.Pop();
                }
            }

            GameObject result = null;
            if (po != null)
            {
                mInUseCount++;
                po.IsPooled = false;
                result = po.gameObject;
                if (autoActive)
                {
                    result.SetActive(true);
                }
            }

            return result;
        }

        private void populatePool(int initialCount)
        {
            for (var index = 0; index < initialCount; index++)
            {
                var obj = Create();
                var po = obj.GetComponent<PoolObject>();
                put(po);
            }
        }

        private void put(PoolObject po)
        {
            po.gameObject.SetActive(false);
            mAvailableObjects.Push(po);
            po.IsPooled = true;
            po.Transform.SetParent(mRootObject.transform, false);
        }

        private void returnObjectToPool(PoolObject poolObject)
        {
            Assert.IsTrue(poolObject.PoolId == Id, string.Format("Trying to add object to incorrect pool {0} {1}", poolObject.PoolId, Id));
            if (poolObject.IsPooled)
            {
#if UNITY_EDITOR
                Debug.LogWarning(poolObject.gameObject.name + " is already in pool. Why are you trying to return it again? Check usage.");
#endif
            }
            else
            {
                mInUseCount--;
                put(poolObject);
            }
        }

        private readonly Stack<PoolObject> mAvailableObjects = new Stack<PoolObject>();
        private readonly GameObject mRootObject;
        private readonly PoolInflationType mInflationType;
        private int mInUseCount;
        private Object mPrefab;
        private bool mAutoActive;
    }
}